/**
 * @file      : queue.c
 * @brief     : 队列驱动源文件
 * @author    : huenrong (huenrong1028@gmail.com)
 * @date      : 2020-07-28 17:26:35
 *
 * @copyright : Copyright (c) 2020  胡恩荣
 *
 */

#include "queue.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>

// 测试队列
struct queue g_test_queue = {0};

/**
 * @brief  初始化循环队列
 * @param  queue_name: 输出参数, 队列名
 * @param  queue_size: 输入参数, 队列缓冲区的总大小
 * @return 成功: 0
 *         失败: -1
 */
int init_queue(struct queue *queue_name, const uint32_t queue_size)
{
    // 分配内存空间
    queue_name->data = (queue_data_type *)malloc(
        (queue_size * sizeof(queue_data_type)));

    // 分配内存空间失败
    if (NULL == queue_name->data)
    {
        return -1;
    }

    queue_name->head = queue_name->tail = 0;
    queue_name->total_size = queue_size;
    queue_name->current_size = 0;

    // 初始化互斥锁
    pthread_mutex_init(&queue_name->queue_mutex, NULL);

    // 初始化条件变量
    pthread_cond_init(&queue_name->queue_cond, NULL);

    return 0;
}

/**
 * @brief  清空循环队列
 * @param  queue_name: 输出参数, 队列名
 */
void clear_queue(struct queue *queue_name)
{
    pthread_mutex_lock(&queue_name->queue_mutex);

    queue_name->head = queue_name->tail = 0;
    queue_name->current_size = 0;

    pthread_mutex_unlock(&queue_name->queue_mutex);
}

/**
 * @brief  获取队列元素的个数
 * @param  queue_name: 输入参数, 队列名
 * @return 队列元素的个数
 */
uint32_t get_queue_length(struct queue queue_name)
{
    return queue_name.current_size;
}

/**
 * @brief  插入数据到循环队列
 * @param  queue_name       : 输出参数, 队列名
 * @param  data    : 输入参数, 需要插入的数据
 * @param  data_len: 输入参数, 需要插入的数据长度
 * @return 实际插入的个数
 */
uint32_t insert_queue(struct queue *queue_name, const queue_data_type *data,
                      const uint32_t data_len)
{
    uint32_t i = 0;

    pthread_mutex_lock(&queue_name->queue_mutex);

    // 数据循环插入队列, 并修改队尾指针
    for (i = 0; i < data_len; i++)
    {
        // 每次插入数据前, 先判断队列是否已满
        if ((queue_name->tail + 1) % queue_name->total_size == queue_name->head)
        {
            pthread_cond_signal(&queue_name->queue_cond);

            pthread_mutex_unlock(&queue_name->queue_mutex);

            printf("aaaaaaaaaaaaa\n");

            return i;
        }

        // 数据插入队列
        queue_name->data[queue_name->tail] = data[i];

        // 修改队尾指针
        queue_name->tail = ((queue_name->tail + 1) % queue_name->total_size);

        // 元素个数增加
        queue_name->current_size++;
    }

    pthread_cond_signal(&queue_name->queue_cond);

    pthread_mutex_unlock(&queue_name->queue_mutex);

    return i;
}

/**
 * @brief  阻塞方式从循环队列中获取数据
 * @param  queue_name: 输出参数, 队列名
 * @param  data      : 输出参数, 获取到的数据
 * @param  data_len  : 输入参数, 指定获取长度
 * @return 实际获取的个数
 */
uint32_t get_queue(struct queue *queue_name, queue_data_type *data,
                   const uint32_t data_len)
{
    uint32_t i = 0;

    pthread_mutex_lock(&queue_name->queue_mutex);

    pthread_cond_wait(&queue_name->queue_cond, &queue_name->queue_mutex);

    for (i = 0; i < data_len; i++)
    {
        // 队列为空, 直接退出
        if (queue_name->head == queue_name->tail)
        {
            pthread_mutex_unlock(&queue_name->queue_mutex);

            return i;
        }

        // 取队列头数据
        data[i] = queue_name->data[queue_name->head];

        // 修改队头指针
        queue_name->head = ((queue_name->head + 1) % queue_name->total_size);

        // 元素个数减小
        queue_name->current_size--;
    }

    pthread_mutex_unlock(&queue_name->queue_mutex);

    return i;
}

/**
 * @brief  超时方式从循环队列中获取数据(超时时间为0, 直接从队列获取数据)
 * @param  queue_name: 输出参数, 队列名
 * @param  data      : 输出参数, 获取到的数据
 * @param  data_len  : 输入参数, 指定获取长度
 * @param  timeout   : 输入参数, 超时时间(单位: 毫秒)
 * @return 超时: -1
 *         其它: 实际获取的个数
 */
int get_queue_with_timeout(struct queue *queue_name, queue_data_type *data,
                           const uint32_t data_len, const uint32_t timeout)
{
    uint32_t i = 0;
    struct timeval start_time; // 等待信号的开始时间
    struct timespec end_time;  // 等待信号的结束时间
    uint8_t timeout_flag = 0;  // 等待信号超时标志

    // 确定信号超时时刻
    gettimeofday(&start_time, NULL);
    end_time.tv_sec = (start_time.tv_sec + (timeout / 1000));
    end_time.tv_nsec = ((start_time.tv_usec * 1000) +
                        ((timeout % 1000) * 1000000));

    pthread_mutex_lock(&queue_name->queue_mutex);

    // 超时方式等待一个信号
    timeout_flag = pthread_cond_timedwait(&queue_name->queue_cond,
                                          &queue_name->queue_mutex,
                                          &end_time);

    // 超时, 直接返回
    if ((timeout > 0) && (ETIMEDOUT == timeout_flag))
    {
        pthread_mutex_unlock(&queue_name->queue_mutex);

        return -1;
    }

    for (i = 0; i < data_len; i++)
    {
        // 队列为空, 直接退出
        if (queue_name->head == queue_name->tail)
        {
            pthread_mutex_unlock(&queue_name->queue_mutex);

            return i;
        }

        // 取队列头数据
        data[i] = queue_name->data[queue_name->head];

        // 修改队头指针
        queue_name->head = ((queue_name->head + 1) % queue_name->total_size);

        // 元素个数减小
        queue_name->current_size--;
    }

    pthread_mutex_unlock(&queue_name->queue_mutex);

    return i;
}

/**
 * @brief  判断循环队列是否为空
 * @param  queue_name: 输入参数, 队列名
 * @return 0: 队列为空
 *         -1: 队列不为空
 */
int queue_is_empty(struct queue queue_name)
{
    pthread_mutex_lock(&queue_name.queue_mutex);

    if (0 == queue_name.current_size)
    {
        pthread_mutex_unlock(&queue_name.queue_mutex);

        return 0;
    }
    else
    {
        pthread_mutex_unlock(&queue_name.queue_mutex);

        return -1;
    }
}

/**
 * @brief  销毁队列
 * @param  queue_name: 输入参数, 队列名
 * @return 成功: 0
 *         失败: -1
 */
int queue_destroy(struct queue *queue_name)
{
    int ret = -1;

    free(queue_name->data);

    ret = pthread_mutex_destroy(&queue_name->queue_mutex);
    if (0 != ret)
    {
        return -1;
    }

    ret = pthread_cond_destroy(&queue_name->queue_cond);
    if (0 != ret)
    {
        return -1;
    }

    queue_name->head = queue_name->tail = 0;

    queue_name->current_size = 0;

    queue_name->total_size = 0;

    return 0;
}
